import { ReqProvider } from "@/core/providers/req.provider";
import { DbService } from "@/db/db.service";
import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { In } from "typeorm"
import { defer, concatMap, map, of, from, delay, forkJoin, Observable, catchError, tap, concat, reduce } from "rxjs";
import IResponse, { IResponseAPI } from "@/services/response.service";
import { IWonewSer } from "@/typing";
import { PubTaskEntity } from "@/db/entitys/pub-task.entity";
import { DyService } from "../dy/dy.service";
import { BiliService } from "../bili/bili.service";
import { AccountEntity } from "@/db/entitys/account.entity";
import { VideoEntity } from "@/db/entitys/video.entity";
import { KsService } from "../ks/ks.service";
import IHelper from "@/services/helper.service";
import { BiliOtherService } from "../bili/bili.other.service";
import IFfmpeg from "@/services/ffmpeg.service";
import IConstants from "@/core/constants";
import * as path from "path";
import { SyncEmailService } from "../sync-email/sync-email.service";
import IGlobal from "@/services/global.service";

@Injectable()
export class PubTaskService {
  constructor(
    private readonly dbService: DbService,
    private readonly reqProvider: ReqProvider,
    private readonly configService: ConfigService,
    private readonly dyService: DyService,
    private readonly biliService: BiliService,
    private readonly ksService: KsService,
    private readonly syncEmailService: SyncEmailService,
  ) {}

  getTaskList(opt?: IWonewSer.QueryFilterParams) {
    const user = this.reqProvider.user
    const page = opt?.page || 1
    const pageSize = opt.pageSize || this.configService.get("PAGE_SIZE", 30)
    const skip = (page - 1) * pageSize
    return defer(() => 
      this.dbService.pubTaskRepository.find({where: {uid: user.id}, order: {id: "desc"}, skip, take: pageSize})
    ).pipe(
      concatMap(list => 
        this.dbService.pubTaskRepository.count().then(total => {
          return {
            list, total, pageSize, page,
          }
        })
      ),
      map(res => IResponse.success(res))
    )
  }
  createOrUpdateTask(id: number, data: Partial<PubTaskEntity>) {
    const user = this.reqProvider.user
    return from(
      id ? this.dbService.pubTaskRepository.findOneByOrFail({uid: user.id, id}) : Promise.resolve(new PubTaskEntity())
    ).pipe(
      concatMap(entity => {
        Object.assign(entity, data, {uid: user.id})
        return this.dbService.pubTaskRepository.save(entity, {reload: true})
      }),
      map(entity => IResponse.success(entity))
    )
  }

  

  createTaskMultiple(id: number, items: {title: string, video_bili_bvid: string}[]) {
    const user = this.reqProvider.user
    return from(
      this.dbService.pubTaskRepository.findOneByOrFail({uid: user.id, id})
    ).pipe(
      concatMap(entity => {
        return this.dbService.pubTaskRepository.findBy({video_bili_bvid: In(items.map(e => e.video_bili_bvid))}).then(exits => ({exits, entity}))
      }),
      concatMap(({entity, exits}) => {
        const {id, ...restProps} = entity
        const exitsBvids = exits.map(e => e.video_bili_bvid)
        const filterItems = items.filter(e => !exitsBvids.includes(e.video_bili_bvid))
        const newItems = filterItems.map(e => {
          const attrs = {
            ...restProps, ...e, video_upload_id: ""
          }
          return Object.assign(new PubTaskEntity(), attrs)
        })
        return this.dbService.pubTaskRepository.save(newItems, {reload: true})
      }),
      map(d => IResponse.success(d))
    )
  }

  startTask(id: number, selected_accounts?: string): Observable<IResponseAPI> {
    const user = this.reqProvider.user
    return from(
      this.dbService.pubTaskRepository.findOneByOrFail({uid: user.id, id})
    ).pipe(
      concatMap(entity => {
        let accountIds = (selected_accounts || entity.account).split(",").map(e => +e.split("_").pop())
        return this.dbService.accountRepository.findBy({id: In([...accountIds])}).then(accounts => {
          return {accounts, task: entity}
        })
      }),
      concatMap(({accounts, task}) => {
        let videoId = +((task.video_upload_id || "").split("_").pop())
        return this.dbService.videoRepository.findOneByOrFail({id: videoId}).then(video => {
          return {accounts, task, video}
        })
      }),
      concatMap(({accounts, task, video}) => {
        // 发布改为串行
        concat(
          ...accounts.map(account => this.pubVideoToAccount(task, account, video))
        ).pipe(
          reduce((ls, acc) => [...ls, acc], []),
        ).subscribe()
        return of(IResponse.success(null, "任务启动成功~"))
      }),
    )
  }

  publishChild(tid: Number, accountId: number) {
    const user = this.reqProvider.user
  }

  pubVideoToAccount(task: PubTaskEntity, account: AccountEntity, video: VideoEntity): Observable<IWonewSer.PublishTaskResult> {
    if (account.type === 1) {
      return this.biliService.publishVideoToAccount(task, account, video).pipe(
        map(res => {
          if (!res.status) {
            this.syncEmailService.reportToEmail(`biliService.publishVideoToAccount ${task.id} ${task.title} error`, res.message).subscribe()
          } else {
            this.syncEmailService.reportToEmail(`biliService.publishVideoToAccount ${task.id} ${task.title} success`, res.message).subscribe()
          }
          return res
        })
      )
    }
    if (account.type === 2) {
      return this.dyService.publishVideoToAccount(task, account, video).pipe(
        map(res => {
          if (!res.status) {
            this.syncEmailService.reportToEmail(`dyService.publishVideoToAccount ${task.id} ${task.title} error`, res.message).subscribe()
          } else {
            this.syncEmailService.reportToEmail(`dyService.publishVideoToAccount ${task.id} ${task.title} success`, res.message).subscribe()
          }
          return res
        })
      )
    }
    if (account.type === 3) {
      return this.ksService.publishVideoToAccount(task, account, video).pipe(
        map(res => {
          if (!res.status) {
            this.syncEmailService.reportToEmail(`ksService.publishVideoToAccount ${task.id} ${task.title} error`, res.message).subscribe()
          } else {
            this.syncEmailService.reportToEmail(`ksService.publishVideoToAccount ${task.id} ${task.title} success`, res.message).subscribe()
          }
          return res
        })
      )
    }
    return of({status: true, message: "123", resp: {} as IWonewSer.BiliPublishTask})
  }

  deleteTask(id: number) {
    const user = this.reqProvider.user
    return defer(() =>
      this.dbService.pubTaskRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap(ent => {
        return this.dbService.pubTaskRepository.remove(ent)
      }),
      delay(1000),
      map(() => IResponse.success(null))
    )
  }

  deleteManyTask(ids: number[]) {
    const user = this.reqProvider.user
    return defer(() =>
      this.dbService.pubTaskRepository.findBy({id: In(ids), uid: user.id})
    ).pipe(
      concatMap(list => {
        const filteredIds = list.map(e => e.id)
        if (ids.some(e => !filteredIds.includes(e))) {
          throw new HttpException("不能删除其他人创建的任务", HttpStatus.BAD_REQUEST)
        }
        return this.dbService.pubTaskRepository.remove(list)
      }),
      map(() => IResponse.success(null))
    )
  }

  getTaskVideo(id: number, config: any) {
    const user = this.reqProvider.user
    const payload: {
      config: IWonewSer.FFmpegConfig,
      pub_task?: PubTaskEntity
      video_path?: string
      result?: IWonewSer.FFmpegBuildTask
      videoEnt?: VideoEntity
    } = {
      config: config as IWonewSer.FFmpegConfig,
    }
    return from(
      this.dbService.pubTaskRepository.findOneByOrFail({uid: user.id, id})
    ).pipe(
      tap(() => {
        this.updateQueueItemState(id, "running")
      }),
      concatMap(ent => {
        if (!ent.video_bili_bvid) {
          throw new HttpException("Get task video failed.", HttpStatus.BAD_REQUEST)
        }
        payload.pub_task = ent
        return this.dbService.biliVideoRepository.findOneBy({bvid: ent.video_bili_bvid})
      }),
      concatMap((biliVideoEnt) => {
        if (biliVideoEnt?.video_path) {
          payload.video_path = biliVideoEnt.video_path
          return of(payload)
        }
        return this.biliService.bbDownload(payload.pub_task.video_bili_bvid).pipe(
          concatMap(res => {
            payload.video_path = res
            return this.dbService.biliVideoRepository.save({bvid: payload.pub_task.video_bili_bvid, video_path: res})
          }),
          map(() => payload)
        )
      }),
      concatMap(() => {
        const task: IWonewSer.FFmpegBuildTask = {
          config, video_path: payload.video_path, video_title: payload.pub_task.title
        } 
        return IFfmpeg.build(task).pipe(
          map(task => payload.result = task)
        )
      }),
      concatMap(() => {
        const videoEnt = new VideoEntity()
        videoEnt.cover = this.convertAbsToStaticPath(payload.result.cover_temp)
        videoEnt.video = this.convertAbsToStaticPath(payload.result.result_temp)
        videoEnt.name = payload.pub_task.title
        videoEnt.uid = user.id
        this.syncEmailService.reportToEmail("GetTaskVideo success", JSON.stringify(videoEnt)).subscribe()
        return this.dbService.videoRepository.save(videoEnt, {reload: true}).then(ent => {
          payload.videoEnt = ent
        })
      }),
      concatMap(() => {
        payload.pub_task.video_upload_id = `${payload.videoEnt.name}_${payload.videoEnt.id}`
        return this.dbService.pubTaskRepository.save(payload.pub_task, {reload: true}).then(ent => {
          payload.pub_task = ent
        })
      }),
      map(() => {
        this.updateQueueItemState(id, "del")
        IResponse.success(payload)
      }),
      catchError(error => {
        this.updateQueueItemState(id, "unset")
        this.syncEmailService.reportToEmail("GetTaskVideo failed", error?.message).subscribe()
        throw error
      })
    )
  }

  updateQueueItemState(id: number, state: "running" | "unset" | "del") {
    const idx = IGlobal.store.getPubVideoQueue.findIndex(e => e.id === id)
    if (idx < 0) return
    if (state === "del") {
      IGlobal.store.getPubVideoQueue.splice(idx, 1)
      return
    }
    const item = IGlobal.store.getPubVideoQueue[idx]
    item.state = state
  }

  getTaskVideoAsync({config, ids}: {config: any, ids: number[]}) {
    const user = this.reqProvider.user
    return defer(() => 
      this.dbService.pubTaskRepository.findBy({uid: user.id, id: In(ids)})
    ).pipe(
      map(list => {
        list.forEach((item) => {
          let existIdx = IGlobal.store.getPubVideoQueue.findIndex(e => e.id === item.id)
          if (existIdx >= 0) {
            IGlobal.store.getPubVideoQueue.splice(existIdx, 1)
          }
          IGlobal.store.getPubVideoQueue.push({id: item.id, config, authorization: this.reqProvider.req.headers["authorization"]})
        })
        return IResponse.success(null, "Add to queue success.")
      })
    )
  }

  getTaskVideoQueue() {
    const ids = IGlobal.store.getPubVideoQueue.map(e => e.id)
    return defer(() => 
      this.dbService.pubTaskRepository.findBy({id: In(ids)})
    ).pipe(
      map(list => {
        return IResponse.success(
          list.map(x => {
            return {
              ...x, config: IGlobal.store.getPubVideoQueue.find(e => e.id === x.id)?.config
            }
          })
        )
      })
    )
    return IResponse.success(IGlobal.store.getPubVideoQueue)
  }

  copyTask(id: number) {
    const user = this.reqProvider.user
    return from(
      this.dbService.pubTaskRepository.findOneByOrFail({uid: user.id, id})
    ).pipe(
      concatMap(ent => {
        let newTask = {...ent}
        delete newTask.id
        newTask.title = `${newTask.title}-复制`
        return this.dbService.pubTaskRepository.save(Object.assign(new PubTaskEntity(), newTask), {reload: true})
      }),
      map(d => IResponse.success(d))
    )
  }

  getTaskSettings() {
    return IResponse.success({settings: IGlobal.store.taskSettings || {}})
  }

  setTaskSettings(settings: any) {
    IGlobal.store.taskSettings = {...IGlobal.store.taskSettings, ...settings}
    return this.getTaskSettings()
  }
  
  convertAbsToStaticPath(abs_path: string): string {
    const staticPrefix = this.configService.get("STATIC_PREFIX", "static")
    return ["", staticPrefix, abs_path.replace(IConstants.publicDir, "").substring(1)].join("/")
  }
}